package ru.ifmo.network.skype.network;


import org.apache.log4j.Logger;
import ru.ifmo.network.skype.GroupsInfoListener;
import ru.ifmo.network.skype.network.data.Group;
import ru.ifmo.network.skype.network.data.MessageType;
import ru.ifmo.network.skype.network.data.UserAddress;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author avhaliullin
 */
public class GroupsController {
    private static final Logger log = Logger.getLogger(GroupsController.class);

    private final File groupsConfig;
    private final Map<Long, Group> groups = new HashMap<>();
    private final List<GroupsInfoListener> listeners = new ArrayList<>();
    private Long selectedGroup = null;
    private MessageHandler messageHandler;
    private MessageHandler sender;

    public GroupsController(File groupsConfig) throws IOException {
        this.groupsConfig = groupsConfig;
        if (groupsConfig.exists() && !groupsConfig.isDirectory()) {
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(groupsConfig));
            try {
                Integer groupsCount = (Integer) inputStream.readObject();
                for (int i = 0; i < groupsCount; i++) {
                    Group group = new Group();
                    group.readExternal(inputStream);
                    groups.put(group.getId(), group);
                    for (GroupsInfoListener listener : listeners) {
                        listener.newGroup(group);
                    }
                }
            } catch (ClassNotFoundException e) {
                //ignore
            }
            inputStream.close();
        }
    }

    public byte[] encrypt(Group group, byte[] message) throws Exception {
//        byte[] msg = new byte[message.length + 2];
//        writeShort((short) message.length, msg, 0);
        return group.encrypt(message);
    }

    public byte[] decrypt(Group group, byte[] message) throws Exception {
        return group.decrypt(message);
    }

    public MessageHandler getOutcomeMessageHandler() {
        return new MessageHandlerAdapter() {
            @Override
            public void handleAudio(UserAddress userAddress, long serialId, byte[] data) {
                Long localSelectedGroup = selectedGroup;
                if (localSelectedGroup == null) {
                    sender.handleAudio(userAddress, serialId, data);
                } else {
                    try {
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
                        out.writeObject(MessageType.AUDIO_DATA.getValue());
                        out.writeObject(serialId);
                        out.writeObject(data);
                        out.close();
                        sender.handleGroup(userAddress, localSelectedGroup, encrypt(groups.get(localSelectedGroup),
                                byteArrayOutputStream.toByteArray()));
                    } catch (Exception e) {
                        log.error(e);
                    }
                }
            }
        };
    }

    public MessageHandler getIncomeMessageHandler() {
        return new MessageHandlerAdapter() {
            @Override
            public void handleAudio(UserAddress userAddress, long serialId, byte[] data) {
                if (selectedGroup == null) {
                    messageHandler.handleAudio(userAddress, serialId, data);
                }
            }

            @Override
            public void handleGroup(UserAddress address, long groupId, byte[] data) {
                if (selectedGroup != null && groupId == selectedGroup) {
                    try {
                        byte[] decryptedData = decrypt(groups.get(groupId), data);
                        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(decryptedData));
                        Integer typeInt = (Integer) in.readObject();
                        MessageType type = MessageType.getByValue(typeInt);
                        switch (type) {
                            case AUDIO_DATA: {
                                Long serialId = (Long) in.readObject();
                                byte[] data2 = (byte[]) in.readObject();
                                messageHandler.handleAudio(address, serialId, data2);
                            }
                            break;
                            case SECURE: {
                                long gid = (Long) in.readObject();
                                byte[] data2 = (byte[]) in.readObject();
                                messageHandler.handleGroup(address, gid, data2);
                            }
                            break;
                            case HELLO: {
                                messageHandler.handleHello(address);
                            }
                            break;
                            case GOODBYE: {
                                messageHandler.handleGoodbye(address);
                            }
                        }
                    } catch (Exception e) {
                        log.error(e);
                    }
                }
            }
        };
    }

    public synchronized void addGroupInfo(Group group) {
        if (!group.equals(groups.put(group.getId(), group))) {
            try {
                ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(groupsConfig));
                outputStream.writeObject(groups.size());
                for (Group g : groups.values()) {
                    g.writeExternal(outputStream);
                }
                outputStream.close();
            } catch (IOException e) {
                log.error("Failed to save groups config", e);
            }
            for (GroupsInfoListener listener : listeners) {
                listener.newGroup(group);
            }
        }
    }

    public void setMessageHandler(MessageHandler messageHandler) {
        this.messageHandler = messageHandler;
    }

    public Map<Long, Group> getGroups() {
        return groups;
    }

    public void setSender(MessageHandler sender) {
        this.sender = sender;
    }

    public void activate(Long groupId) {
        this.selectedGroup = groupId;
    }

    public void addListener(GroupsInfoListener listener) {
        listeners.add(listener);
        for (Group group : groups.values()) {
            listener.newGroup(group);
        }
    }

    public Group getActiveGroup() {
        return groups.get(selectedGroup);
    }
}
